/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../regs.h"

#include "../smc_tbls.h"
#include "../smc.h"

#include "ctx.h"
#include "private.h"
#include "initial.h"
#include "emergency.h"
#include "ulv.h"
#include "driver.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
void smc_mc_arb_tbl_dump(struct smc_mc_arb_tbl *tbl)
{
	u8 i;

	L("SMC_MC_ARB_TBL START");

	L("arb_freq_fx_current=0x%02x",tbl->arb_freq_fx_current);

	for (i = 0; i < 3; ++i)
		L("rsvd[%u]=0x%02x",i,tbl->rsvd[i]);

	for (i = 0; i < SMC_MC_ARB_TBL_SETS_N_MAX; ++i) {
		u32 tmp;
		u8 j;

		tmp = get_unaligned_be32(&tbl->sets[i].dram_timing_x_0);
		L("sets[%u].dram_timing_x_0=0x%08x",i,tmp);

		tmp = get_unaligned_be32(&tbl->sets[i].dram_timing_x_1);
		L("sets[%u].dram_timing_x_1=0x%08x",i,tmp);

		L("sets[%u].refresh_rate=0x%02x",i,tbl->sets[i].refresh_rate);
		L("sets[%u].burst_time=0x%02x",i,tbl->sets[i].burst_time);

		for (j = 0; j < 2; ++j)
			L("sets[%u].pad[%u]=0x%02x",i,j,tbl->sets[i].pad[j]);
	}

	L("SMC_MC_ARB_TBL END");
}
#endif

/* eng_clk is in units of 10kHz */
static u8 refresh_rate(struct ctx *ctx, u32 eng_clk)
{
	u32 dram_rows_n;
	u32 dram_rows_n_log2_10; /* unit of 1024 rows (2^10) */
	u32 mc_arb_ram_cfg;
	u32 mc_seq_misc_0;
	u32 dram_refresh_rate_log2_3; /* unit of 8 xHz (2^3) */
	u32 dram_refresh_rate;

	mc_arb_ram_cfg = rr32(ctx->dev, MC_ARB_RAM_CFG);
	dram_rows_n_log2_10 = get(MARC_ROWS_N, mc_arb_ram_cfg);

	if (dram_rows_n_log2_10 >= 4) /* cap the number of rows */
		dram_rows_n = 16384; /* 2 ^ (4 + 10) */
	else
		dram_rows_n = BIT(dram_rows_n_log2_10 + 10);

	mc_seq_misc_0 = rr32(ctx->dev, MC_SEQ_MISC_0);
	dram_refresh_rate_log2_3 = get(MSM0_DRAM_REFRESH_RATE, mc_seq_misc_0);	
	dram_refresh_rate = BIT(dram_refresh_rate_log2_3 + 3);

	return (u8)(((eng_clk * 10) * dram_refresh_rate / dram_rows_n - 32)
									/ 64);
}

long smc_mc_arb_tbl_set_compute(struct ctx *ctx, struct smc_mc_arb_reg_set *set,
						u32 eng_clk, u32 mem_clk)
{
	struct dev_drv_data *dd;
	long r;
	u32 dram_timing_x_0;
	u32 dram_timing_x_1;
	u32 mc_arb_burst_time;
	u8 burst_time;

	set->refresh_rate = refresh_rate(ctx, eng_clk);

	/*
	 * The following atombios table will store the timings in
	 * MC_ARB_DRAM_TIMING_0_0 MC_ARB_DRAM_TIMING_0_1
	 * MC_ARB_BURST_TIME(MABT_STATE_0), namely the state 0 of mc arg regs.
	 */
	dd = pci_get_drvdata(ctx->dev);
	r = atb_eng_mem_timings_program(dd->atb, eng_clk, mem_clk);
	if (r == -ATB_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to set the engine memory timings\n");
		return -SI_ERR;
	}

	dram_timing_x_0 = rr32(ctx->dev, MC_ARB_DRAM_TIMING_0_0);
	dram_timing_x_1 = rr32(ctx->dev, MC_ARB_DRAM_TIMING_0_1);
	mc_arb_burst_time = rr32(ctx->dev, MC_ARB_BURST_TIME);
	burst_time = get(MABT_STATE_0, mc_arb_burst_time);
	
	put_unaligned_be32(dram_timing_x_0, &set->dram_timing_x_0);
	put_unaligned_be32(dram_timing_x_1, &set->dram_timing_x_1);
	set->burst_time = burst_time;
	return 0;
}

static long initial_emergency_init(struct ctx *ctx, 
					struct smc_state_tbl *smc_state_tbl,
					struct smc_mc_arb_tbl *smc_mc_arb_tbl)
{
	u32 eng_clk;
	u32 mem_clk;
	struct smc_mc_arb_reg_set *initial_set;
	long r;

	/*
	 * we don't use the atombios pp boot state table, then we get the
	 * required information directly from the smc table
	 */
	eng_clk = get_unaligned_be32(&smc_state_tbl->initial_lvl.eng_clk.clk);
	mem_clk = get_unaligned_be32(&smc_state_tbl->initial_lvl.mem_clk.clk);

	initial_set = &smc_mc_arb_tbl->sets[MC_ARB_SET_IDX_INITIAL_EMERGENCY];

	r = smc_mc_arb_tbl_set_compute(ctx, initial_set, eng_clk, mem_clk);
	if (r == -SI_ERR) {
		dev_err(&ctx->dev->dev, "dyn_pm:unable to init the memory controller arb register set for the initial state\n");
		return -SI_ERR;
	}
	return 0;
}

long smc_mc_arb_tbl_init(struct ctx *ctx, struct smc_state_tbl *smc_state_tbl,
					struct smc_mc_arb_tbl *smc_mc_arb_tbl)
{
	long r;

	LOG("smc memory controller arbiter table init");

	r = initial_emergency_init(ctx, smc_state_tbl, smc_mc_arb_tbl);
	if (r == -SI_ERR)
		return -SI_ERR;
	r = smc_mc_arb_tbl_ulv_init(ctx, smc_mc_arb_tbl);
	if (r == -SI_ERR)
		return -SI_ERR;
	/* XXX:in initing phase, the driver state is the initial state */

	/* set the hw register set to use for the mc arb */
	smc_mc_arb_tbl->arb_freq_fx_current = MAC_MC_CG_ARB_FREQ_F1;
	return 0;
}
